//The MIT License
//
//Copyright (c) 2009 nodchip
//
//Permission is hereby granted, free of charge, to any person obtaining a copy
//of this software and associated documentation files (the "Software"), to deal
//in the Software without restriction, including without limitation the rights
//to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
//copies of the Software, and to permit persons to whom the Software is
//furnished to do so, subject to the following conditions:
//
//The above copyright notice and this permission notice shall be included in
//all copies or substantial portions of the Software.
//
//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
//FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
//AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
//LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
//OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
//THE SOFTWARE.
package tv.dyndns.kishibe.server;

import java.io.File;
import java.io.FilenameFilter;
import java.io.IOException;
import java.nio.charset.Charset;
import java.sql.SQLException;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import tv.dyndns.kishibe.client.packet.PacketRankingData;
import tv.dyndns.kishibe.server.database.Database;

import com.google.common.collect.HashMultiset;
import com.google.common.collect.Multiset;
import com.google.common.io.Files;
import com.google.gwt.thirdparty.guava.common.collect.Sets;
import com.google.inject.Guice;
import com.google.inject.Inject;
import com.google.inject.Injector;

public class BadUserManager {
	private static final Logger logger = Logger.getLogger(BadUserManager.class.getName());
	private final Database database;

	@Inject
	public BadUserManager(Database database) {
		this.database = database;

		ThreadPool.get().addDailyTask(new Runnable() {
			@Override
			public void run() {
				try {
					updateCheater();
				} catch (IOException | SQLException e) {
					logger.log(Level.WARNING, "チートユーザーのアップデートに失敗しました", e);
				}
			}
		});
	}

	public void addIgnoreUserCode(final int userCode, final int targetUserCode) {
		ThreadPool.get().execute(new Runnable() {
			public void run() {
				try {
					database.addIgnoreUserCode(userCode, targetUserCode);
				} catch (SQLException e) {
					logger.log(Level.WARNING, "無視ユーザーの追加に失敗しました", e);
				}
			}
		});
	}

	public void removeIgnoreUserCode(final int userCode, final int targetUserCode) {
		ThreadPool.get().execute(new Runnable() {
			public void run() {
				try {
					database.removeIgnoreUserCode(userCode, targetUserCode);
				} catch (SQLException e) {
					logger.log(Level.WARNING, "無視ユーザーの削除に失敗しました", e);
				}
			}
		});
	}

	public boolean isLimitedUser(int userCode, String remoteAddress) {
		try {
			return database.getLimitedUserCodes().contains(userCode)
					|| database.getLimitedRemoteAddresses().contains(remoteAddress);
		} catch (SQLException e) {
			logger.log(Level.WARNING, "制限ユーザーの確認に失敗しました", e);
			return false;
		}
	}

	public void addLimitedUser(final int userCode, final String remoteAddress) {
		ThreadPool.get().execute(new Runnable() {
			@Override
			public void run() {
				try {
					database.addLimitedUserCode(userCode);
				} catch (SQLException e) {
					logger.log(Level.WARNING, "制限ユーザーのユーザーコードの追加に失敗しました", e);
				}

				if (!remoteAddress.equals("127.0.0.1")) {
					try {
						database.addLimitedRemoteAddress(remoteAddress);
					} catch (SQLException e) {
						logger.log(Level.WARNING, "制限ユーザーのIPアドレスの追加に失敗しました", e);
					}
				}
			}
		});
	}

	private static final FilenameFilter LOG_FILENAME_FILTER = new FilenameFilter() {
		@Override
		public boolean accept(File dir, String name) {
			return name.startsWith("catalina.") && name.endsWith(".log");
		}
	};
	private static final Pattern REGISTER_PATTERN = Pattern
			.compile("ServiceImpl\\{method=register, userCode=([0-9]+?)\\}");
	private static final Pattern NOTIFY_GAME_FINISHED_PATTERN = Pattern
			.compile("ServiceImpl\\{method=notifyGameFinished, userCode=([0-9]+?),");

	// private static class Counter implements Comparable<Counter> {
	// int userCode;
	// int register;
	// int notifyGameFinished;
	//
	// @Override
	// public int compareTo(Counter o) {
	// return -((register - notifyGameFinished) - (o.register - o.notifyGameFinished));
	// }
	// }

	private void updateCheater() throws IOException, SQLException {
		Set<Integer> userCodes = Sets.newHashSet();
		Multiset<Integer> registerCounter = HashMultiset.create();
		Multiset<Integer> notifyGameFinishedCounter = HashMultiset.create();
		for (File file : new File("/var/log/tomcat6").listFiles(LOG_FILENAME_FILTER)) {
			// System.out.println(file);

			String body = Files.toString(file, Charset.forName("utf-8"));

			Matcher registerMatcher = REGISTER_PATTERN.matcher(body);
			while (registerMatcher.find()) {
				int userCode = Integer.valueOf(registerMatcher.group(1));
				userCodes.add(userCode);
				registerCounter.add(userCode);
			}

			Matcher notifyGameFinishedMatcher = NOTIFY_GAME_FINISHED_PATTERN.matcher(body);
			while (notifyGameFinishedMatcher.find()) {
				int userCode = Integer.valueOf(notifyGameFinishedMatcher.group(1));
				userCodes.add(userCode);
				notifyGameFinishedCounter.add(userCode);
			}
		}

		// List<Counter> counters = Lists.newArrayList();
		//
		// for (int userCode : registerCounter.elementSet()) {
		// Counter counter = new Counter();
		// counter.userCode = userCode;
		// counter.register = registerCounter.count(userCode);
		// counter.notifyGameFinished = notifyGameFinishedCounter.count(userCode);
		//
		// counters.add(counter);
		// }
		//
		// Collections.sort(counters);

		// for (Counter counter : counters) {
		// System.out.printf("userCode=%8d register=%4d notifyGameFinished=%4d\n",
		// counter.userCode, counter.register, counter.notifyGameFinished);
		// }

		for (PacketRankingData user : database.getRankingData().get(3)) {
			int userCode = user.userCode;
			if (registerCounter.count(userCode) >= 3
					&& registerCounter.count(userCode) > notifyGameFinishedCounter.count(userCode) * 2) {
				database.addLimitedUserCode(userCode);
				logger.log(
						Level.INFO,
						"制限ユーザーに追加しました: " + user + " register=" + registerCounter.count(userCode)
								+ " notifyGameFinished="
								+ notifyGameFinishedCounter.count(userCode));
			}
		}
	}

	public static void main(String[] args) throws IOException, SQLException {
		Injector injector = Guice.createInjector(new QMACloneModule());
		BadUserManager badUserManager = injector.getInstance(BadUserManager.class);
		badUserManager.updateCheater();
		ThreadPool.get().shutdown();
	}
}
